﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using iNet.Collections;

namespace iNet.Media.Xmp
{
	/// <summary>
	/// XMP 中繼資料。
	/// </summary>
	public class XmpMetadata : MetadataBase, IXmpMetadata
	{
		#region 靜態欄位
		/// <summary>
		/// 適用於 IMetadata.Author 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> AuthorPropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.DublinCore, "creator"),
		});
		/// <summary>
		/// 適用於 IMetadata.Copyright 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> CopyrightPropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.DublinCore, "rights"),
		});
		/// <summary>
		/// 適用於 IMetadata.CreationTime 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> CreationTimePropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.XmpBasic, "CreateDate"),
		});
		/// <summary>
		/// 適用於 IMetadata.Keywords 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> KeywordsPropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.DublinCore, "subject"),
		});
		/// <summary>
		/// 適用於 IMetadata.LastWriteTime 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> LastWriteTimePropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.XmpBasic, "ModifyDate"),
		});
		/// <summary>
		/// 適用於 IMetadata.Rating 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> RatingPropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.XmpBasic, "Rating"),
		});
		/// <summary>
		/// 適用於 IMetadata.Software 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> SoftwarePropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.XmpBasic, "CreatorTool"),
		});
		/// <summary>
		/// 適用於 IMetadata.Title 屬性的 XMP Property 名稱。
		/// </summary>
		public static readonly ReadOnlyCollection<XmpPropertyName> TitlePropertyNames = new ReadOnlyCollection<XmpPropertyName>(new XmpPropertyName[]{
			new XmpPropertyName(CommonXmpSchemas.DublinCore, "title"),
		});
		#endregion
		#region 欄位
		List<XmpProperty> _PropertyList = new List<XmpProperty>();
		IList<XmpProperty> _XmpProperties;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 XmpMetadata 執行個體。
		/// </summary>
		public XmpMetadata()
		{
			_XmpProperties = _PropertyList;
		}
		/// <summary>
		/// 初始化新的 XmpMetadata 執行個體。
		/// </summary>
		/// <param name="properties">XMP Property 列表。</param>
		/// <param name="isReadOnly">是否為唯讀。</param>
		public XmpMetadata(IEnumerable<XmpProperty> properties, bool isReadOnly)
			: base(null, isReadOnly)
		{
			if (properties != null)
				_PropertyList.AddRange(properties);
			_XmpProperties = (isReadOnly ? new ReadOnlyCollection<XmpProperty>(_PropertyList) : (IList<XmpProperty>)_PropertyList);
		}
		/// <summary>
		/// 初始化新的 XmpMetadata 執行個體。
		/// </summary>
		/// <param name="template">作為範本的中繼資料。</param>
		/// <param name="isReadOnly">是否為唯讀。</param>
		public XmpMetadata(IMetadata template, bool isReadOnly)
			: base(template, isReadOnly)
		{
			_XmpProperties = (isReadOnly ? new ReadOnlyCollection<XmpProperty>(_PropertyList) : (IList<XmpProperty>)_PropertyList);
		}
		#endregion
		#region AddProperty
		/// <summary>
		/// 新增一個 XMP Property。
		/// </summary>
		/// <param name="name">XMP Property 名稱。</param>
		/// <param name="value">XMP Property 的值。</param>
		/// <returns>新增的 XMP Property。</returns>
		/// <exception cref="ArgumentNullException">name 或 value 為 Null (Visual Basic 為 Nothing)。</exception>
		protected XmpProperty AddProperty(XmpPropertyName name, XmpPropertyValue value)
		{
			//確認參數
			if (name == null)
				throw new ArgumentNullException("name");
			if (value == null)
				throw new ArgumentNullException("value");

			//加入項目
			XmpProperty property = new XmpProperty(name, value);
			_PropertyList.Add(property);
			return property;
		}
		#endregion
		#region Author
		/// <summary>
		/// 取得或設定媒體的作者。
		/// </summary>
		public override string[] Authors
		{
			get
			{
				return this.GetStringArray(this.FindProperties(AuthorPropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(AuthorPropertyNames, AuthorPropertyNames[0], value, XmpArrayType.Ordered);
			}
		}
		#endregion
		#region Clone
		/// <summary>
		/// 建立可修改的 XmpMetadata 複本。
		/// </summary>
		/// <returns>XmpMetadata 複本。</returns>
		public override object Clone()
		{
			return new XmpMetadata(this, false);
		}
		#endregion
		#region CopyFrom
		/// <summary>
		/// 從指定的中繼資料複製屬性。
		/// </summary>
		/// <param name="metadata">指定的中繼資料。</param>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		public override void CopyFrom(IMetadata metadata)
		{
			//確認狀態
			if (this.IsReadOnly)
				throw new InvalidOperationException();

			//複製
			if (metadata != null && metadata != this)
			{
				//複製 XMP Property
				if (metadata is IXmpMetadata)
					_PropertyList.AddRange((metadata as IXmpMetadata).XmpProperties);

				//複製屬性
				this.Authors = metadata.Authors;
				this.Copyright = metadata.Copyright;
				this.CreationTime = metadata.CreationTime;
				this.Keywords = metadata.Keywords;
				this.LastWriteTime = metadata.LastWriteTime;
				this.Rating = metadata.Rating;
				this.Software = metadata.Software;
				this.Title = metadata.Title;
			}
		}
		#endregion
		#region Copyright
		/// <summary>
		/// 取得或設定媒體的版權資訊。
		/// </summary>
		public override string Copyright
		{
			get
			{
				return this.GetValue<string>(this.FindProperties(CopyrightPropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(CopyrightPropertyNames, CopyrightPropertyNames[0], value);
			}
		}
		#endregion
		#region CreationTime
		/// <summary>
		/// 取得或設定媒體建立時間。
		/// </summary>
		public override DateTime? CreationTime
		{
			get
			{
				return this.GetValue<DateTime?>(this.FindProperties(CreationTimePropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(CreationTimePropertyNames, CreationTimePropertyNames[0], value);
			}
		}
		#endregion
		#region FindProperties
		/// <summary>
		/// 搜尋所有指定名稱的 XMP Property，並將結果依條件順序排序。
		/// </summary>
		/// <param name="names">XMP Property 名稱。</param>
		/// <returns>所有符合名稱的 XMP Property。</returns>
		/// <exception cref="ArgumentNullException">names 為 Null (Visual Basic 為 Nothing)。</exception>
		public List<XmpProperty> FindProperties(IList<XmpPropertyName> names)
		{
			//確認參數
			if (names == null)
				throw new ArgumentNullException("names");

			//搜尋項目
			List<XmpProperty> list = new List<XmpProperty>();
			for (int i = 0, nameCount = names.Count; i < nameCount; ++i)
			{
				XmpPropertyName name = names[i];
				for (int j = _PropertyList.Count - 1; j >= 0; --j)
				{
					XmpProperty property = _PropertyList[j];
					if (property != null && property.Name.Equals(name))
						list.Add(property);
				}
			}
			return list;
		}
		#endregion
		#region FindProperty
		/// <summary>
		/// 搜尋指定名稱的 XMP Property。
		/// </summary>
		/// <param name="name">XMP Property 名稱。</param>
		/// <returns>第一個符合名稱的 XMP Property，Null (Visual Basic 為 Nothing) 表示沒有任何 XMP Property 符合。</returns>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		public XmpProperty FindProperty(XmpPropertyName name)
		{
			//確認參數
			if (name == null)
				throw new ArgumentNullException("name");

			//搜尋
			foreach (XmpProperty property in _PropertyList)
			{
				if (property != null && property.Name.Equals(name))
					return property;
			}
			return null;
		}
		#endregion
		#region GetAsReadOnly
		/// <summary>
		/// 取得唯讀的 XmpMetadata。
		/// </summary>
		/// <returns>唯讀的 XmpMetadata。</returns>
		public override IMetadata GetAsReadOnly()
		{
			if (this.IsReadOnly)
				return this;
			return new XmpMetadata(this, true);
		}
		#endregion
		#region GetStringArray
		/// <summary>
		/// 從指定的 XMP Property 列表中，取出第一個可以轉換成字串陣列的值。
		/// </summary>
		/// <param name="properties">XMP Property 列表。</param>
		/// <returns>第一個可以轉換的字串陣列，若沒有符合的 XMP Property 將傳回 Null (Visual Basic 為 Nothing)。</returns>
		/// <exception cref="ArgumentNullException">properties 為 Null (Visual Basic 為 Nothing)。</exception>
		protected string[] GetStringArray(IList<XmpProperty> properties)
		{
			//確認參數
			if (properties == null)
				throw new ArgumentNullException("properties");

			//取得陣列
			for (int i = 0, count = properties.Count; i < count; ++i)
			{
				XmpProperty property = properties[i];
				if (property != null)
				{
					XmpPropertyValue value = property.Value;
					if (value is ArrayXmpPropertyValue)
					{
						object extractedValue = value.ExtractValue();
						if (extractedValue is Array)
						{
							string[] strArray;
							if ((extractedValue as Array).TryConvertTo<string>(out strArray))
								return strArray;
						}
						else if (extractedValue is MultiLanguageString)
							return new string[] { extractedValue.ToString() };
					}
					else if (value is TextXmpPropertyValue)
						return (value as TextXmpPropertyValue).Value.Split(';');
				}
			}
			return null;
		}
		#endregion
		#region GetValue
		/// <summary>
		/// 從指定的 XMP Property 列表中，取出第一個可以轉換成指定型別的值。
		/// </summary>
		/// <typeparam name="T">指定的值型別。</typeparam>
		/// <param name="properties">XMP Property 列表。</param>
		/// <returns>第一個可以轉換成指定型別的值，若沒有符合的 XMP Property 將傳回型別預設值。</returns>
		/// <exception cref="ArgumentNullException">properties 為 Null (Visual Basic 為 Nothing)。</exception>
		protected T GetValue<T>(IList<XmpProperty> properties)
		{
			//確認參數
			if (properties == null)
				throw new ArgumentNullException("properties");

			//取得值
			for (int i = 0, count = properties.Count; i < count; ++i)
			{
				XmpProperty property = properties[i];
				if (property != null)
				{
					T value;
					if (property.Value.ExtractValue().TryCast<T>(out value))
						return value;
				}
			}
			return default(T);
		}
		#endregion
		#region Keywords
		/// <summary>
		/// 取得或設定媒體的關鍵字。
		/// </summary>
		public override string[] Keywords
		{
			get
			{
				return this.GetStringArray(this.FindProperties(KeywordsPropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(KeywordsPropertyNames, KeywordsPropertyNames[0], value, XmpArrayType.Ordered);
			}
		}
		#endregion
		#region LastWriteTime
		/// <summary>
		/// 取得或設定媒體最近更新時間。
		/// </summary>
		public override DateTime? LastWriteTime
		{
			get
			{
				return this.GetValue<DateTime?>(this.FindProperties(LastWriteTimePropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(LastWriteTimePropertyNames, LastWriteTimePropertyNames[0], value);
			}
		}
		#endregion
		#region Rating
		/// <summary>
		/// 取得或設定媒體的評價。
		/// </summary>
		public override int? Rating
		{
			get
			{
				return this.GetValue<int?>(this.FindProperties(RatingPropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(RatingPropertyNames, RatingPropertyNames[0], value);
			}
		}
		#endregion
		#region RemoveProperties
		/// <summary>
		/// 移除所有指定名稱的 XMP Property。
		/// </summary>
		/// <param name="name">XMP Property 名稱。</param>
		/// <returns>移除的 XMP Property 數量。</returns>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected int RemoveProperties(XmpPropertyName name)
		{
			//確認參數
			if (name == null)
				throw new ArgumentNullException("name");

			//移除項目
			return _PropertyList.RemoveAll((item) =>
			{
				if (item != null)
					return item.Name.Equals(name);
				return false;
			});
		}
		/// <summary>
		/// 移除所有指定名稱的 XMP Property。
		/// </summary>
		/// <param name="names">XMP Property 名稱列表。</param>
		/// <returns>移除的 XMP Property 數量。</returns>
		/// <exception cref="ArgumentNullException">names 為 Null (Visual Basic 為 Nothing)。</exception>
		protected int RemoveProperties(IEnumerable<XmpPropertyName> names)
		{
			//確認參數
			if (names == null)
				throw new ArgumentNullException("names");

			//移除項目
			return _PropertyList.RemoveAll((item) =>
			{
				if (item != null)
					return names.Contains<XmpPropertyName>(item.Name);
				return false;
			});
		}
		#endregion
		#region Software
		/// <summary>
		/// 取得或設定製作媒體的軟體名稱。
		/// </summary>
		public override string Software
		{
			get
			{
				return this.GetValue<string>(this.FindProperties(SoftwarePropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(SoftwarePropertyNames, SoftwarePropertyNames[0], value);
			}
		}
		#endregion
		#region Title
		/// <summary>
		/// 取得或設定媒體的標題。
		/// </summary>
		public override string Title
		{
			get
			{
				return this.GetValue<string>(this.FindProperties(TitlePropertyNames));
			}
			set
			{
				if (this.IsReadOnly)
					throw new InvalidOperationException();
				this.UpdateValue(TitlePropertyNames, TitlePropertyNames[0], value);
			}
		}
		#endregion
		#region UpdateValue
		/// <summary>
		/// 更新指定的 XMP Property 值。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <param name="arrayType">XMP 陣列類型。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, string[] value, XmpArrayType arrayType)
		{
			//建立陣列
			ArrayXmpPropertyValue arrayValue;
			if (value != null && value.Length > 0)
			{
				arrayValue = new ArrayXmpPropertyValue(arrayType);
				for (int i = 0; i < value.Length; ++i)
					arrayValue.Items.Add(new TextXmpPropertyValue(value[i] != null ? value[i] : ""));
			}
			else
				arrayValue = null;

			//更新屬性
			this.UpdateValue(removingProperties, name, arrayValue);
		}
		/// <summary>
		/// 更新指定的 XMP Property 值。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, string value)
		{
			this.UpdateValue(removingProperties, name, (value != null ? new TextXmpPropertyValue(value) : null));
		}
		/// <summary>
		/// 更新指定的 XMP Property 值。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, DateTime? value)
		{
			this.UpdateValue(removingProperties, name, (value != null ? new DateXmpPropertyValue(value.Value) : null));
		}
		/// <summary>
		/// 更新指定的 XMP Property 值。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, double? value)
		{
			this.UpdateValue(removingProperties, name, (value != null ? new RealXmpPropertyValue(value.Value) : null));
		}
		/// <summary>
		/// 更新指定的 XMP Property 值。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, Rational? value)
		{
			this.UpdateValue(removingProperties, name, (value != null ? new RationalXmpPropertyValue(value.Value) : null));
		}
		/// <summary>
		/// 更新指定的 XMP Property 值。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, int? value)
		{
			this.UpdateValue(removingProperties, name, (value != null ? new IntegerXmpPropertyValue(value.Value) : null));
		}
		/// <summary>
		/// 更新指定的 XMP Property。
		/// </summary>
		/// <param name="removingProperties">更新前必須先移除的 XMP Property，Null (Visual Basic 為 Nothing) 表示只移除欲更新的 XMP Property。</param>
		/// <param name="name">欲更新的 XMP Property 名稱。</param>
		/// <param name="value">XMP Property 值，Null (Visual Basic 為 Nothing) 表示刪除此 XMP Property。</param>
		/// <exception cref="ArgumentNullException">name 為 Null (Visual Basic 為 Nothing)。</exception>
		protected void UpdateValue(IEnumerable<XmpPropertyName> removingProperties, XmpPropertyName name, XmpPropertyValue value)
		{
			//確認參數
			if (name == null)
				throw new ArgumentNullException("name");

			//確認刪除的目標必須包含本身
			if (removingProperties == null)
				removingProperties = new XmpPropertyName[] { name };
			else if (!removingProperties.Contains<XmpPropertyName>(name))
			{
				List<XmpPropertyName> list = new List<XmpPropertyName>(removingProperties);
				list.Add(name);
				removingProperties = list;
			}

			//移除項目
			this.RemoveProperties(removingProperties);

			//加入項目
			if (value != null)
				this.AddProperty(name, value);
		}
		#endregion
		#region XmpProperties
		/// <summary>
		/// 取得原始 XMP Property 集合。
		/// </summary>
		public IList<XmpProperty> XmpProperties
		{
			get
			{
				return _XmpProperties;
			}
		}
		#endregion
	}
}
